package com.gua.encrypt.advice;

import static com.gua.common.response.AjaxResultEnum.DECRYPT_ERROR;
import static com.gua.common.response.AjaxResultEnum.FAIL;
import static com.gua.common.response.AjaxResultEnum.PARAM_ERROR;
import static com.gua.common.response.AjaxResultEnum.TIME_MILLIS_ERROR;

import java.io.IOException;
import java.lang.reflect.Type;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gua.common.exception.BusinessExecption;
import com.gua.encrypt.annotation.Decryption;
import com.gua.encrypt.constants.CryptoConstant;
import com.gua.encrypt.dto.EncryptRequestDTO;
import com.gua.encrypt.dto.EncryptDataBaseDTO;
import com.gua.encrypt.utils.AESUtil;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @author huangjj
 * @description: requestBody 自动解密
 */
@ConditionalOnProperty(prefix = "crypto", name = "type", havingValue = CryptoConstant.TYPE_AES, matchIfMissing = false)
@Slf4j
@ControllerAdvice
public class AESDecryptRequestBodyAdvice implements RequestBodyAdvice {

	@Autowired
	private ObjectMapper objectMapper;
	
	public AESDecryptRequestBodyAdvice() {
		log.info("AESDecryptRequestBodyAdvice init!!");
	}

	/**
	 * 方法上有DecryptionAnnotation注解的，进入此拦截器
	 * 
	 * @param methodParameter 方法参数对象
	 * @param targetType      参数的类型
	 * @param converterType   消息转换器
	 * @return true，进入，false，跳过
	 */
	@Override
	public boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		return methodParameter.hasMethodAnnotation(Decryption.class);
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		return inputMessage;
	}

	/**
	 * 转换之后，执行此方法，解密，赋值
	 * 
	 * @param body          spring解析完的参数
	 * @param inputMessage  输入参数
	 * @param parameter     参数对象
	 * @param targetType    参数类型
	 * @param converterType 消息转换类型
	 * @return 真实的参数
	 */
	@SneakyThrows
	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		// 获取request
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
		if (servletRequestAttributes == null) {
			throw new BusinessExecption(FAIL.getCode(), "request error");
		}
		HttpServletRequest request = servletRequestAttributes.getRequest();
		// 获取数据
		ServletInputStream inputStream = request.getInputStream();
		EncryptRequestDTO encryptRequestDTO = objectMapper.readValue(inputStream, EncryptRequestDTO.class);

		if (encryptRequestDTO == null || StringUtils.isBlank(encryptRequestDTO.getText())) {
			throw new BusinessExecption(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
		}
		// 获取加密的数据
		String text = encryptRequestDTO.getText();
		//放入解密之前的数据
		request.setAttribute(CryptoConstant.INPUT_ORIGINAL_DATA, text);
		// 解密
		String decryptText = null;
		try {
			decryptText = AESUtil.decrypt(text);
		} catch (Exception e) {
			log.error("afterBodyRead>>", e);
			throw new BusinessExecption(DECRYPT_ERROR.getCode(), DECRYPT_ERROR.getMsg());
		}
		if (StringUtils.isBlank(decryptText)) {
			throw new BusinessExecption(DECRYPT_ERROR.getCode(), DECRYPT_ERROR.getMsg());
		}
		// 放入解密之后的数据
		request.setAttribute(CryptoConstant.INPUT_DECRYPT_DATA, decryptText);
		// 获取结果
		Object result = objectMapper.readValue(decryptText, body.getClass());
		// 强制所有实体类必须继承EncryptDataBaseDTO类，设置时间戳
		if (result instanceof EncryptDataBaseDTO) {
			EncryptDataBaseDTO encryptDataBaseDTO = (EncryptDataBaseDTO) result;
			// 获取时间戳
			Long currentTimeMillis = encryptDataBaseDTO.getCurrentTimeMillis();
			String random = encryptDataBaseDTO.getRandom();
			String sign = encryptRequestDTO.getSign();
			// 有效期 60秒
			long effective = 60 * 1000;
			// 时间差
			long expire = System.currentTimeMillis() - currentTimeMillis;
			// 是否在有效期内
			if (Math.abs(expire) > effective) {
				throw new BusinessExecption(TIME_MILLIS_ERROR.getCode(), TIME_MILLIS_ERROR.getMsg());
			}
			// 返回解密之后的数据
			return result;
		} else {
			throw new BusinessExecption(FAIL.getCode(),
					String.format("请求参数类型：%s 未继承：%s", result.getClass().getName(), EncryptDataBaseDTO.class.getName()));
		}
	}

	/**
	 * 如果body为空，转为空对象
	 * 
	 * @param body          spring解析完的参数
	 * @param inputMessage  输入参数
	 * @param parameter     参数对象
	 * @param targetType    参数类型
	 * @param converterType 消息转换类型
	 * @return 真实的参数
	 */
	@SneakyThrows
	@Override
	public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		String typeName = targetType.getTypeName();
		Class<?> bodyClass = Class.forName(typeName);
		return bodyClass.newInstance();
	}
}
